package com.heima.wemedia.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.heima.common.aliyun.GreenImageScan;
import com.heima.common.aliyun.GreenTextScan;
import com.heima.feign.article.IArticleClient;
import com.heima.feign.schedule.IScheduleClient;
import com.heima.file.MinIoTemplate;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.schedule.dto.Task;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmSensitive;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.OcrUtil;
import com.heima.utils.ProtostuffUtil;
import com.heima.utils.SensitiveWordUtil;
import com.heima.wemedia.mapper.WmChannelMapper;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.mapper.WmSensitiveMapper;
import com.heima.wemedia.mapper.WmUserMapper;
import com.heima.wemedia.service.AutoScanService;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.tess4j.TesseractException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@Transactional
public class AutoScanServiceImpl implements AutoScanService {
    @Autowired
    private IArticleClient iArticleClient;
    @Autowired
    private WmNewsMapper wmNewsMapper;
    @Autowired
    private WmChannelMapper wmChannelMapper;
    @Autowired
    private WmUserMapper wmUserMapper;
    @Autowired
    private MinIoTemplate minIoTemplate;
    @Autowired
    private GreenTextScan greenTextScan;
    @Autowired
    private GreenImageScan greenImageScan;
    @Autowired
    private WmSensitiveMapper wmSensitiveMapper;
    @Autowired
    private IScheduleClient scheduleClient;

    //自媒体文章审核
    @Override
    @Async  //标明当前方法是一个异步方法
    public void autoScanWmNews(Integer newsId) throws Exception {
        //0.入参判空及处理
        if (newsId == null) {
            log.warn("文章审核的入参为空");
            return;
        }
        WmNews wmNewsFromDB = wmNewsMapper.selectById(newsId);
        if (wmNewsFromDB == null) {
            log.warn("新闻" + newsId + "为空");
            return;
        }
        String title = wmNewsFromDB.getTitle();//标题
        String images = wmNewsFromDB.getImages();//封面图
        String content = wmNewsFromDB.getContent();

        //1.提取文字  文字:标题+内容
        String totalText = getTotalText(title, content);

        //2.提取图片  图片:封面图+内容图
        List<byte[]> imageBytesList = getImageBytesList(images, content);

        //调用OCR工具类提取图片中的文字
        String image2Text = OCRImage2Text(imageBytesList);
        totalText = totalText + "_" + image2Text;

        //调用阿里云审核之前 先调用DFA算法审核
        Boolean checkResult = DFACheckResult(totalText);
        if (!checkResult) {
            log.warn("DFA文字审核不通过");
            updateWmNews(newsId, (short) 2, "DFA文字审核不通过", null);
            return;
        }

        //3.调用阿里云,审核文字和图片
        Map textScan = greenTextScan.greeTextScan(totalText);//审核文字
        Boolean textBoolean = checkScanResult(textScan, newsId);
        if (!textBoolean) {
            log.warn("文字审核不通过");
            return;
        }
        Map imageScan = greenImageScan.imageScan(imageBytesList); //审核图片
        Boolean ImageBoolean = checkScanResult(imageScan, newsId);
        if (!ImageBoolean) {
            log.warn("图片审核不通过");
            return;
        }
        //5.调用文章保存
        ArticleDto dto = wmNews2ArticleDto(wmNewsFromDB);//转类型
        ResponseResult responseResult = iArticleClient.saveArticle(dto);//feign远程调用
        if (responseResult.getCode() != 200 || responseResult.getCode() == null) {
            log.warn("feign远程调用文章保存失败");
            return;
        }

        Long articleId = (Long) responseResult.getData();
        Boolean updateWmNews = updateWmNews(newsId, (short) 9, "审核通过", articleId);
    }

    //OCR图片文字提取
    private String OCRImage2Text(List<byte[]> imageBytesList) throws IOException, TesseractException {
        //入参判空
        if (CollectionUtils.isEmpty(imageBytesList)) {
            return "";
        }

        StringBuilder builder = new StringBuilder();
        for (byte[] image : imageBytesList) {
            //调用OCR工具类,提取文字内容
            ByteArrayInputStream inputStream = new ByteArrayInputStream(image);
            BufferedImage read = ImageIO.read(inputStream);
            String text = OcrUtil.doOcr(read);//调用OCR工具类提取图片文字
            builder.append("_").append(text);//使用StringBuilder拼接
        }

        return builder.toString();
    }


    //定时器:消费任务 每秒执行一次  feign远程调用IScheduleClient
    @Scheduled(fixedDelay = 60 * 1000)
    public void getTask() {
        System.out.println("=======定时器执行=======");

        //执行消费方法
        ResponseResult responseResult = scheduleClient.poll();
        if (responseResult == null || responseResult.getCode() != 200) {
            log.warn("定时器消费任务失败");
            return;
        }
        String data = (String) responseResult.getData();
        if (StringUtils.isBlank(data)) {
            log.warn("没有可消费的任务");
            return;
        }
        Task task = JSON.parseObject(data, Task.class);
        if (task == null) {
            return;
        }

        //获取参数
        byte[] parameters = task.getParameters();
        WmNews wmNews = ProtostuffUtil.deserialize(parameters, WmNews.class);//反序列化
        if (wmNews == null) {
            log.warn("参数缺失");
            return;
        }
        Integer newsId = wmNews.getId();

        //调用autoScanWmNews方法
        try {
            autoScanWmNews(newsId);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    //DFA算法审核
    private Boolean DFACheckResult(String text) {
        if (StringUtils.isBlank(text)) {
            return true;
        }

        //到mysql中查询所有敏感词
        QueryWrapper<WmSensitive> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("sensitives");
        List<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(wmSensitives)) {
            return true;
        }

        //提取敏感字段
        List<String> words = wmSensitives
                .stream()   //开启流
                .filter(Objects::nonNull)   //过滤空字段
                .map(WmSensitive::getSensitives)    //提取WmSensitive中的sensitives字段
                .collect(Collectors.toList());  //创建成一个新集合
        if (CollectionUtils.isEmpty(words)) {
            return true;
        }

        //调用DFA算法,进行敏感词初始化:将敏感词拆分,形参树状结构
        SensitiveWordUtil.initMap(words);

        //调用DFA算法,进行敏感词匹配
        Map<String, Integer> map = SensitiveWordUtil.matchWords(text);

        //判断结果: 为空则表明没有匹配到敏感词
        return CollectionUtils.isEmpty(map);//map为空返回true
    }

    //检查阿里云审核结果 并返回
    private Boolean checkScanResult(Map scanResult, Integer newsId) {
        String suggestion = (String) scanResult.get("suggestion");
        if ("block".equals(suggestion)) {
            updateWmNews(newsId, (short) 2, "审核不通过", null);
            log.warn("阿里云审核不通过");
            return false;
        }
        if ("review".equals(suggestion)) {
            updateWmNews(newsId, (short) 3, "转人工审核", null);
            log.warn("需要重新审核");
            return false;
        }

        if (StringUtils.isBlank(suggestion)) {
            log.warn("阿里云审核通过为空");
        }
        if ("pass".equals(suggestion)) {
            log.info("阿里云审核通过");
        }
        updateWmNews(newsId, (short) 9, "审核通过", null);
        return true;
    }

    //修改参数:status审核状态   reason拒绝理由    articleId发布文章的Id若审核不通过,则不发布,为null
    private Boolean updateWmNews(Integer newsId, short status, String reason, Long articleId) {
        WmNews wmNews = new WmNews();
        wmNews.setId(newsId);//条件

        wmNews.setStatus(status);
        wmNews.setReason(reason);
        if (articleId != null) {
            wmNews.setArticleId(articleId);
        }

        int update = wmNewsMapper.updateById(wmNews);
        return update > 0;
    }


    /**
     * wmNews转换成ArticleDto,以便iArticleClient接口调用文章保存
     */
    private ArticleDto wmNews2ArticleDto(WmNews wmNews) {
        if (wmNews == null) {
            return null;
        }
        ArticleDto articleDto = new ArticleDto();

        //1.拷贝wmNews中相同字段
        BeanUtils.copyProperties(wmNews, articleDto);

        //2.处理不同字段:authorName(作者名称)   channelName(频道名称)   layout(文章布局 单图多图?)
        articleDto.setLayout(wmNews.getType());//layout(文章布局)

        Integer channelId = wmNews.getChannelId();
        if (channelId != null) {
            WmChannel wmChannel = wmChannelMapper.selectById(channelId);
            if (wmChannel != null) {
                articleDto.setChannelName(wmChannel.getName());//channelName(频道名称)
            }
        }

        Integer userId = wmNews.getUserId();
        if (userId != null) {
            articleDto.setAuthorId(userId.longValue());//authorId(作者Id)
            WmUser wmUser = wmUserMapper.selectById(userId);
            if (wmUser != null) {
                articleDto.setAuthorName(wmUser.getName());//authorName(作者名称)
            }
        }

        return articleDto;
    }

    /**
     * 提取文字
     *
     * @param title   标题
     * @param content 内容
     * @return String
     */
    private String getTotalText(String title, String content) {
        //0.入参判空
        List<Map> maps = JSON.parseArray(content, Map.class);
        if (CollectionUtils.isEmpty(maps)) {
            log.warn("content内容为空");
            return title;
        }

        //提取content里面的文字部分
        StringBuilder textTotal = new StringBuilder("title:")
                .append(title)
                .append("_content:");//单线程环境 循环字符串拼接 选用StringBuilder
        for (Map map : maps) {
            if (CollectionUtils.isEmpty(map)) {
                continue;
            }
            Object type = map.get("type");
            if ("text".equals(type)) {
                String text = (String) map.get("value");
                textTotal.append(text).append(" ");//将文章部分拼接至total
            }
        }
        return textTotal.toString();
    }

    /**
     * 提取图片
     *
     * @param images  封面图
     * @param content 内容
     * @return List
     */
    private List<byte[]> getImageBytesList(String images, String content) {
        //构建结果集
        ArrayList<String> imageList = new ArrayList<>();
        ArrayList<byte[]> bytesList = new ArrayList<>();
        // 1. 判断images是否为空 添加封面图
        if (StringUtils.isNotEmpty(images)) {
            String[] coverImages = images.split(",");//图片存储地址是用","隔开的
            imageList.addAll(Arrays.asList(coverImages));
        }
        //2. 判断content是否为空 添加内容图
        List<Map> maps = JSON.parseArray(content, Map.class);
        if (!CollectionUtils.isEmpty(maps)) {
            for (Map map : maps) {
                if (CollectionUtils.isEmpty(map)) {
                    continue;
                }
                Object type = map.get("type");
                if ("image".equals(type)) {
                    String image = (String) map.get("value");
                    imageList.add(image);
                }
            }
        }
        //3. 将imageList中String类型转为byte类型
        for (String image : imageList) {
            if (StringUtils.isBlank(image)) {
                continue;
            }
            byte[] bytes = minIoTemplate.downLoadFile(image);
            bytesList.add(bytes);
        }
        return bytesList;
    }
}
